home *** CD-ROM | disk | FTP | other *** search
/ Aminet 39 / Aminet 39 (2000)(Schatztruhe)[!][Oct 2000].iso / Aminet / misc / math / number.lha / number / number.c < prev    next >
Encoding:
C/C++ Source or Header  |  1998-09-16  |  14.3 KB  |  671 lines

  1. #include <stdio.h>
  2. #include <ctype.h>
  3. /*
  4.  * number
  5.  *
  6.  *    Number is a program that counts in lots of languages.
  7.  *    It was originally written during a sanity break while
  8.  *    I was writing my PhD thesis.  That version got left
  9.  *    on a machine somewhere in upstate New York.  This one
  10.  *    was done while on leave in Grenoble, after realizing
  11.  *    that I hadn't written a computer program in over two
  12.  *    months.
  13.  *
  14.  *    Number is inspired, of course, from /usr/games/number, but
  15.  *    it uses a series of grammars that define counting in
  16.  *    different languages.  The language that is used to
  17.  *    write the grammars is described below, in evalrule().
  18.  *    If you write any new grammars, I'd greatly appreciate
  19.  *    having them.  Grammars aren't very hard to write, if
  20.  *    you know how to count in something that isn't defined
  21.  *    here.  The longest grammar (french) only has 30 rules
  22.  *    and 5 macros, and correctly pronounces any number less
  23.  *    1,000,000,000,000.  The shortest is for cantonese, which
  24.  *    has 14 rules.
  25.  *
  26.  * A note on the output of number:
  27.  *
  28.  *    The characters that are output conform to the TIRA
  29.  *    character representation standard.  Essentially, strings
  30.  *    in anything except the latin alphabet (what you're reading
  31.  *    now) are preceded by an indication of the alphabet that
  32.  *    they are part of.  The exceptions to this are mandarin,
  33.  *    cantonese and japanese.  These three are written in
  34.  *    pin-yin, roughly Wade Giles, and romanji, respectively.
  35.  *    The only other thing special about this format is that
  36.  *    accents and tone markings are given in [] brackets
  37.  *    before the letter to which they are attached.
  38.  *
  39.  *    TIRA stands for Textual Information Retrieval and Analysis
  40.  *    research group, and is a research group at the University
  41.  *    of Chicago containing computer and information scientists,
  42.  *    literary scholars and linguists.  TIRA is working on a
  43.  *    research environment for doing textual research.  Watch
  44.  *    this space.
  45.  *
  46.  * Copyright 1987, Scott Deerwester.
  47.  *
  48.  *    This code may be freely distributed and copied, provided
  49.  *    that a copy of this notice accompanies all copies and
  50.  *    that no copy is sold for profit.
  51.  */
  52.  
  53. /*
  54.  *    Constants for array bounds.  Both of these are overkill.
  55.  */
  56.  
  57. #define    MAXRULES    100
  58. #define    MAXSPECIALS    50
  59.  
  60. /*
  61.  *    Structure to hold macro definitions.
  62.  */
  63.  
  64. struct {
  65.     char    c;
  66.     char    *rule;
  67. } specials [MAXSPECIALS];
  68.  
  69. int    nspecials = 0;
  70. int    maxdigits;
  71.  
  72. /*
  73.  *    Definition of a grammar rule.
  74.  */
  75.  
  76. struct {
  77.     int    base;
  78. #ifdef    COND
  79.     int    cond;
  80. #endif    COND
  81.     char    *rule;
  82. } rule[MAXRULES];
  83.  
  84. int    nrules;
  85. char    *lang = "english";        /* You can change this if you like */
  86. char    *malloc ();
  87. unsigned long    parsenumber();
  88. long    atol(), random();
  89.  
  90. int    dbgflag = 0;
  91.  
  92. main (argc, argv)
  93.      char *argv[];
  94. {
  95.         int errflg = 0;
  96.  
  97.     chkdbg ();
  98. #ifndef _AMIGA
  99.     srandom (getpid ());
  100. #else
  101.     srandom (time (NULL));
  102. #endif
  103.     domaxdigits ();
  104. /*
  105.  *    Someday, maybe, I'll enable this to take a number
  106.  *    on the command line.
  107.  */
  108.  
  109.         switch (argc) {
  110.     case 1:    break;
  111.     case 2:    lang = argv[1];    break;
  112.     default:
  113.         errflg++;
  114.             break;
  115.     }
  116.  
  117.     if (errflg)
  118.     {
  119.             fprintf (stderr, "Usage: number [language]\n");
  120.         exit (0);
  121.     }
  122.  
  123. /*
  124.  *    read_grammar finds the grammar for the language and
  125.  *    reads it in.  It exits if it can't find the grammar.
  126.  */
  127.     read_grammar (lang);
  128. /*
  129.  *    Main loop.  Read in numbers.  Make sure that the input
  130.  *    is a number, and spell it in the requested language.
  131.  */
  132.     while (1)
  133.     {
  134.         char lbuf [512];
  135.         register i, l;
  136.         unsigned long u;
  137.         long n;
  138.  
  139.         if (isatty (0))
  140.             printf ("> ");
  141.  
  142.         if (!gets (lbuf))
  143.         {
  144.             break;
  145.         }
  146.  
  147.         if ((l = strlen (lbuf)) > maxdigits)
  148.         {
  149.             printf ("My limit is ");
  150.             for (i = 0; i < maxdigits; i++)
  151.                 putchar ('9');
  152.             putchar ('\n');
  153.             continue;
  154.         } else if (l == 0)
  155.             continue;
  156.  
  157.         n = 0;
  158.         if (sscanf (lbuf, "%ld", &n) != 1)
  159.         {
  160.             printf ("%s is not a non-negative integer.\n", lbuf);
  161.             continue;
  162.         }
  163.  
  164.         if (n < 0)
  165.         {
  166.             printf ("I don't handle negative numbers.\n");
  167.             continue;
  168.         }
  169.  
  170.         sscanf (lbuf, "%ld", &u);
  171.         spell (u, 0);
  172.         outchar ('\n');
  173.     }
  174.     outchar ('\n');
  175. }
  176.  
  177. domaxdigits ()
  178. {
  179.     unsigned long maxint = 0;
  180.     register i;
  181.     char str [128];
  182.  
  183.     for (i = 0; i < sizeof (long) * 8; i++)
  184.         maxint |= 1 << i;
  185.     sprintf (str, "%lu", maxint);
  186.     maxdigits = strlen (str) - 1;
  187.  
  188. dbg ("domaxdigits computes %lu as %d reliable digits.\n", maxint, maxdigits);
  189. }
  190.  
  191. /*
  192.  *    read_to_eol is equivalent to fgets, except that it
  193.  *    reads the string into a temporary buffer, allocates
  194.  *    enough space for it, and copies the string into the
  195.  *    allocated space.  In other words, it does what fgets()
  196.  *    would do if C had proper memory management. :-)
  197.  */
  198.  
  199. char *read_to_eol (fp)
  200.     FILE *fp;
  201. {
  202.     char    *tmpbuf, *cp;
  203.     char    rbuf [512];
  204.     register l = 0;
  205.  
  206.     cp = rbuf;
  207.     while (1)
  208.     {
  209.         fgets (cp, sizeof (rbuf) - l, fp);
  210.         l = strlen (rbuf);
  211.         if (rbuf [l - 2] != '\\')
  212.             break;
  213.         cp = rbuf + l - 2;
  214.         if (getc (fp) != '\t')
  215.         {
  216.             fprintf (stderr, "read_to_eol didn't find a tab\n");
  217.             exit (0);
  218.         }
  219.         if (l >= sizeof (rbuf))
  220.         {
  221.             fprintf (stderr, "rule too long in read_to_eol\n");
  222.             exit (0);
  223.         }
  224.     }
  225.     
  226.     tmpbuf = malloc (l = strlen (rbuf));
  227.     rbuf [l - 1] = '\0';    /* get rid of the newline */
  228.  
  229.     strcpy (tmpbuf, rbuf);
  230.  
  231.     return (tmpbuf);
  232. }
  233.  
  234.  
  235. static char filename[128];
  236.  
  237. /*
  238.  *    Cutesy error messages.  They all say the same thing.
  239.  */
  240.  
  241. char *errorfmt[] =
  242. {
  243.     "No se habla \"%s\".  Se habla:\n",
  244.     "I don't speak \"%s\".  I speak:\n",
  245.     "On ne parle pas \"%s\" ici.  On parle plut[^]ot:\n",
  246.     "Ich kann nicht \"%s\" sprechen.  Ich spreche:\n",
  247.     "Ng[?]o [_]m s[^]ik g[']ong \"%s\" w[`]a.  Ng[?]o s[^]ik:\n",
  248.     "W[?]o b[`]u hu[`]e \"%s\".  W[?]o hu[`]e:\n",
  249.     "\CYR'Ya' n'ye' govor'yu' po-\"%s\".  'Ya' govor'yu':\n",
  250.     "Nt[`]e \"%s\" kan m[`]en.  N[`]e be:\n"
  251. };
  252.  
  253. #define    nerrfmt    8
  254.  
  255. #ifndef _AMIGA
  256. rand (n)
  257. {
  258.     return (random () % n);
  259. }
  260. #endif
  261.  
  262. /*
  263.  *    read_grammar depends on a set of grammar files being
  264.  *    found in GRAMMARDIR.  It expects to find a file with
  265.  *    the name of its parameter, which it opens and reads.
  266.  *    If it can't find one, it prints out a message saying
  267.  *    that it doesn't speak the language, and lists the
  268.  *    known languages by exec'ing /bin/ls.  Note that this
  269.  *    is equivalent to exitting.  It simply puts each of
  270.  *    the rules and macros into arrays.  The format of the
  271.  *    rules in the grammar files is:
  272.  *
  273.  *        n \t rule
  274.  *
  275.  *    where "n" is the base unit of the rule, and "rule"
  276.  *    conforms to the syntax described below in evalrule().
  277.  *    Macros definitions are of the form:
  278.  *
  279.  *        / \t c \t rule
  280.  *
  281.  *    where "c" is the character to be expanded.  The character
  282.  *    must not be a reserved character.
  283.  *
  284.  *    Grammars may also contain comment lines, which begin with
  285.  *    a '#'.
  286.  */
  287. read_grammar (lang)
  288.     char *lang;
  289. {
  290.     register i, c;
  291.     FILE *fp;
  292.  
  293.     strcat (filename, GRAMMARDIR);
  294.     strcat (filename, lang);
  295.  
  296.     if ((fp = fopen (filename, "r")) == NULL)
  297.     {
  298.         if ((fp = fopen (lang, "r")) == NULL)
  299.         {
  300. #ifndef _AMIGA
  301.             printf (errorfmt [rand (nerrfmt)], lang);
  302.             execl ("/bin/ls", "number-ls", GRAMMARDIR, 0);
  303. #else
  304.             printf (errorfmt [rand () % nerrfmt], lang);
  305.             exit (system ("dir "GRAMMARDIR));
  306. #endif
  307.         }
  308.     }
  309.  
  310.     for (i = 0; !feof (fp);)
  311.     {
  312. #ifdef    COND
  313.         rule[i].cond = 0;
  314. #endif    COND
  315.  
  316.         if ((c = getc (fp)) == '/')
  317.         {
  318.             register j;
  319.  
  320.             while ((c = getc (fp)) == '\t')
  321.                 ;
  322.             j = nspecials++;
  323.             specials[j].c = c;
  324.             while ((c = getc (fp)) == '\t')
  325.                 ;
  326.             ungetc (c, fp);
  327.             specials[j].rule = read_to_eol (fp);
  328.  
  329. dbg ("macro '%c': %s\n", specials[j].c, specials[j].rule);
  330.  
  331.             continue;
  332.         } else if (c == EOF)
  333.         {
  334.             break;
  335.         } else if (c == '\n')
  336.         {
  337.             continue;
  338.         } else if (c == '#')
  339.         {
  340.             while (getc (fp) != '\n')
  341.                 ;
  342.             continue;
  343.         } else if (!isdigit (c))
  344.         {
  345.             printf ("Read a '%c' in rule %d\n", c, i);
  346.             break;
  347.         } else
  348.             ungetc (c, fp);
  349.  
  350.         if (fscanf (fp, "%d", &rule[i].base) != 1)
  351.             break;
  352.  
  353.         if ((c = getc (fp)) != '\t')
  354.         {
  355. #ifdef COND
  356.             rule[i].cond = c;
  357. #endif COND
  358.             while (getc (fp) != '\t')
  359.                 ;
  360.         }
  361.  
  362.         rule[i].rule = read_to_eol (fp);
  363.  
  364. dbg ("rule %d: %d %s\n", i, rule[i].base, rule[i].rule);
  365.  
  366.         i++;
  367.     }
  368.     nrules = i;
  369. }
  370.  
  371. /*
  372.  *    spell is the function called to spell a number.  It
  373.  *    is initially called with condition 'I' (init).  This
  374.  *    is a hack to get around the problem of when to pronounce
  375.  *    0.  Spell essentially just figures out what the appropriate
  376.  *    rule is, and calls evalrule() to do the work.
  377.  */
  378. spell (n, level)
  379.     unsigned long n;
  380. {
  381.     register i;
  382.  
  383.     if (n == 0 && level)
  384.         return;
  385.  
  386.     for (i = nrules - 1; rule[i].base > n; i--)
  387.         ;
  388.  
  389.     evalrule (rule[i].rule, rule[i].base, n, level);
  390. }
  391.  
  392. /*
  393.  * next
  394.  *    This is a simple function to bounce around in strings
  395.  *    with a syntax that includes balanced parens and double
  396.  *    quotes. There's something like this in Icon, but this
  397.  *    program is in C, so...
  398.  */
  399. char *next (s, c)
  400.     char *s, c;
  401. {
  402.     register char *e;
  403.  
  404.     for (e = s; *e != c; e++)
  405.     {
  406.         if (*e == '"')
  407.             e = next (e + 1, '"');
  408.         if (*e == '(' && c != '"')
  409.             e = next (e + 1, ')');
  410.     }
  411.     return (e);
  412. }
  413.  
  414. /*
  415.  *    evalrule does the dirty work.  It takes a rule, a
  416.  *    base, and a number, and prints the number according
  417.  *    to the rule.  Rules may use the following characters:
  418.  *
  419.  *    B    the base
  420.  *    %    n % base
  421.  *    /    n / base
  422.  *    ,    no-op
  423.  *    "..."    for strings
  424.  *
  425.  *    conditionals are of the form:
  426.  *
  427.  *        (L C R \t rule)
  428.  *
  429.  *    where L and R are either a special character or a
  430.  *    number, and C is one of '>', '<', '=' and '~', meaning,
  431.  *    of course, less than, greater than, equal, and not equal.
  432.  *    Conditionals are evaluated by doconditional(), which
  433.  *    evaluates the condition, and, if it is true, evaluates
  434.  *    the rule.
  435.  *
  436.  *    To give an example of a rule, taken from the grammar
  437.  *    for mandarin:
  438.  *
  439.  *    10    / "sh[']i" %
  440.  *
  441.  *    means that if the largest number that is smaller than
  442.  *    the number we're trying to say is 10, then we say the
  443.  *    number by saying the number divided by 10, followed
  444.  *    by the word "sh[']i", followed by the remainder of the
  445.  *    number divided by ten.  In other words, to say 23,
  446.  *    you say (23 / 10) = 2, then "sh[']i", then (23 % 10) = 3,
  447.  *    or 2 "sh[']i" 3.  After evaluating the rules for 2 and
  448.  *    3, the string "e[`]r sh[']i s[^]an" is printed.
  449.  */
  450.  
  451. evalrule (rule, base, n, level)
  452.     char    *rule;
  453.     unsigned long    n;
  454.     int    base, level;
  455. {    
  456.     register j, c;
  457.  
  458. dbg ("evalrule (\"%s\", %d, %ld)\n", rule, base, n);
  459.  
  460.     while (c = *rule)
  461.     {
  462.         if (isdigit (c))
  463.         {
  464.             spell (atol (rule), level + 1);
  465.             while (isdigit (*++rule))
  466.                 ;
  467.             continue;
  468.         } else switch (c) {
  469.         case ',':    break;
  470.         case 'B':    spell ((long) base, level + 1);    break;
  471.         case '%':    spell (n % base, level + 1);    break;
  472.         case '/':    spell (n / base, level + 1);    break;
  473.  
  474.         case '"':    while ((c = *++rule) != '"')
  475.                     outchar (c);
  476.                 break;
  477.         case '(':    docondition (rule, base, n, level);
  478.                 rule = next (rule + 1, ')');
  479.                 break;
  480.         default:    for (j = 0; j < nspecials; j++)
  481.                 {
  482.                     if (specials[j].c == c)
  483.                     {
  484.                     evalrule (specials[j].rule, base,
  485.                           n, level);
  486.                     break;
  487.                     }
  488.                 }
  489.                 if (j == nspecials)
  490.                     outchar (c);
  491.         }
  492.         rule++;
  493.     }
  494. }
  495. /*
  496.  *    docondition evaluates conditionals, which are delimited
  497.  *    by parentheses, and which contain two parts: a very
  498.  *    simple Boolean expression and a rule.  The Boolean
  499.  *    expression can, at the moment, only be a simple comparison.
  500.  *    OR's (if the conditions are exclusive) can be done by
  501.  *    putting multiple conditions in a row, and AND's by
  502.  *    making the rule a conditional.  docondition calls
  503.  *    parsecond (parse conditional) to pick out the various
  504.  *    parts of the conditional, evaluates the comparison,
  505.  *    and calls evalrule with the rule as an argument if the
  506.  *    comparison evaluates to true.
  507.  *
  508.  *    Two additional special characters that are accepted here
  509.  *    are:
  510.  *
  511.  *        L    Current recursion level
  512.  *        #    The number itself
  513.  */
  514. docondition (rule, base, n, level)
  515.     char    *rule;
  516.     unsigned long    n;
  517.     int    base, level;
  518. {
  519.     char    subrule [128];
  520.     unsigned long    leftside, rightside;
  521.     int    truth;
  522.     char    comparator;
  523.  
  524. /*
  525.  *    This is to check for bad grammars or buggy parser.
  526.  */
  527.     if (!parsecond (rule, base, n, level,
  528.             &leftside, &comparator, &rightside, subrule))
  529.     {
  530.         printf ("Gagged on rule \"%s\"\n", rule);
  531.         return;
  532.     }
  533.  
  534.     switch (comparator) {
  535.     case '>':    truth = leftside > rightside;    break;
  536.     case '=':    truth = leftside == rightside;    break;
  537.     case '<':    truth = leftside < rightside;    break;
  538.     case '~':    truth = leftside != rightside;    break;
  539.     }
  540.  
  541. dbg ("docondition (%d, %d, %d %c %d) -> %s\n",
  542.     base, n, leftside, comparator, rightside,
  543.     truth ? subrule : "FAILS");
  544.  
  545.     if (!truth)
  546.         return;
  547.  
  548.     evalrule (subrule, base, n, level);
  549. }
  550.  
  551. /*
  552.  *    parsecond parses the rule according to the base,
  553.  *    and assigns the parts to the variables passed
  554.  *    as arguments.
  555.  */
  556.  
  557. parsecond (rule, base, n, level, lp, cp, rp, subrule)
  558.     char    *rule, *cp, *subrule;
  559.     unsigned long    *lp, *rp, n;
  560.     int    base, level;
  561. {
  562.     char    *index(), *rindex();
  563.     register char *start, *end;
  564.     char    leftstring[20], rightstring[20];
  565.  
  566.     if (sscanf (rule, "(%s %c %s", leftstring, cp, rightstring) != 3)
  567.     {
  568.  
  569. dbg ("parsecond failed sscanf (\"%s\", ...)\n", rule);
  570.  
  571.         return (0);
  572.     }
  573.  
  574.     *rp = parsenumber (rightstring, base, n, level);
  575.     *lp = parsenumber (leftstring, base, n, level);
  576.  
  577.     if (!(start = index (rule, '\t')))
  578.     {
  579.  
  580. dbg ("parsecond couldn't find a tab in \"%s\"\n", rule);
  581.  
  582.         return (0);
  583.     }
  584.  
  585.     end = next (++start, ')');
  586.  
  587.     while (start < end)
  588.         *subrule++ = *start++;
  589.         
  590.     *subrule = '\0';
  591.     return (1);
  592. }
  593.  
  594. /*
  595.  *    parsenumber figures out the numerical value of the
  596.  *    string that it is passed, based on the base and the
  597.  *    number n.
  598.  */
  599.  
  600. unsigned long parsenumber (s, base, n, level)
  601.     unsigned long n;
  602.     char *s;
  603. {
  604.     if (isdigit (s[0]))
  605.         return (atoi (s));
  606.  
  607.     switch (s[0]) {
  608.     case '/':    return (n / base);
  609.     case '%':    return (n % base);
  610.     case '#':    return (n);
  611.     case 'L':    return (level);
  612.     case 'B':    return (base);
  613.     default:    fprintf (stderr, "bad number string \"%s\"\n", s);
  614.             return (-1);
  615.     }
  616. }
  617.  
  618. /*
  619.  *    outchar is a slightly clever version of putchar.  It
  620.  *    won't put a space at the beginning of a line, and it
  621.  *    won't put two spaces in a row.
  622.  */
  623.  
  624. outchar (c)
  625. {
  626.     static    lastspace = 0,
  627.         bol = 1;
  628.  
  629.     if ((lastspace || bol) && c == ' ')
  630.         return;
  631.  
  632.     if (c == '\n')
  633.         bol = 1;
  634.     else
  635.         bol = 0;
  636.  
  637.     if (c == ' ')
  638.         lastspace = 1;
  639.     else
  640.         lastspace = 0;
  641.  
  642.     putchar (c);
  643. }
  644.  
  645. /*
  646.  *    Well, see, I had this bug, and I left my debugger in
  647.  *    Chicago, and...
  648.  */
  649.  
  650. dbg (fmt, a1, a2, a3, a4, a5, a6, a7, a8, a9)
  651.     char *fmt, *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8, *a9;
  652. {
  653.     int tmpdbgflag = dbgflag;
  654.  
  655.     if (dbgflag > 0)
  656.     {
  657.         dbgflag = 0;
  658.         fprintf (stderr, fmt, a1, a2, a3, a4, a5, a6, a7, a8, a9);
  659.         dbgflag = tmpdbgflag;
  660.     }
  661. }
  662.  
  663. chkdbg ()
  664. {
  665.     extern char *getenv ();
  666.     register char *cp;
  667.  
  668.     if ((dbgflag == 0) && (cp = getenv ("DEBUG=")))
  669.         dbgflag = atoi (cp);
  670. }
  671.